package com.julian.apps.Sensors;


import android.app.Activity;
import android.app.AlertDialog;
import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;

//import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
//import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.Sensor;
//import android.os.Bundle;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Vector;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils;

//import android.graphics.*;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.DefaultHttpClient;


public class Sensors extends Activity {

     private ViewData viewData;
     private SensorManager mSensorManager;
     
     private static int MAXPOINTS = 1000;
     private static float MAXACCEL = 30.f;
     private static float MAXMAGNET = 50.f;
     private static int SensorRate = SensorManager.SENSOR_DELAY_FASTEST;
     
     private static long t0;
     private static long lastt = 0;
     private static long deltat;
     // jeremy's parameters
     private static float lastx = 0,  lasty = 0;
     private static float lastax = 0, lastay = 0, lastaz = 0;
     private static float totala = 0, totalj = 0;
     private boolean user_turned_on_GPS = false;
     

     private static int lastSensorMeasurementType = 0;
     
     public static Vector accelMeasurements, magnetMeasurements;
     // jeremy's file-writing tools
     public java.io.FileOutputStream fos;
     private static final int MASK = 0xff;
     
     public boolean bInit = false;
     
     //const by Michael Wu
     public boolean intrip = true;

     
   
     @Override
     protected void onCreate(Bundle savedInstanceState) {

          super.onCreate(savedInstanceState);
 
          mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
          mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorRate);
          mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),SensorRate);
          //mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ALL),SensorRate);

          final LocationManager locationManager = (LocationManager) getSystemService( Context.LOCATION_SERVICE );
          
          if ( !locationManager.isProviderEnabled(locationManager.GPS_PROVIDER) ) {
              buildAlertMessageNoGPS();
              user_turned_on_GPS = locationManager.isProviderEnabled(locationManager.GPS_PROVIDER);
          }
        
          if(!bInit) {
        	  accelMeasurements = new Vector(MAXPOINTS);
        	  magnetMeasurements = new Vector(MAXPOINTS);
        	  bInit = true;
        	  
        	  // TODO: make it save to a logical filename
        	  String FILENAME = "test_file";
        	  intrip = true;
        	  try {
				fos = openFileOutput(FILENAME, Context.MODE_WORLD_READABLE);
				String username = getUsername();
				try {
					fos.write(intToByteArray(username.length()));
					fos.write(username.getBytes());
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        	  
          }

          
          viewData = new ViewData(this);
          setContentView(viewData);
  		  viewData.postInvalidate();  	
          
     }

     private void debugToast(String msg) {
    	 Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
     }
     
     // A message to be shown on startup if the GPS is not enabled.
     private void buildAlertMessageNoGPS() {
    	 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
    	 builder.setMessage("Your GPS seems to be disabled, do you want to enable it?")
            .setCancelable(false)
            .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                public void onClick(@SuppressWarnings("unused") final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
                    launchGPSOptions(); 
                }
            })
            .setNegativeButton("No", new DialogInterface.OnClickListener() {
                public void onClick(final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
                     dialog.cancel();
                }
            });
     final AlertDialog alert = builder.create();
     alert.show();
 }     
     
     // A message to be shown on shutdown if the GPS was enabled for this session only.
     private void buildAlertMessageUserTurnedOnGPS() {
    	 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
    	 builder.setMessage("Your GPS was manually enabled for this session, do you want to disable it?")
            .setCancelable(false)
            .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                public void onClick(@SuppressWarnings("unused") final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
                    launchGPSOptions(); 
                }
            })
            .setNegativeButton("No", new DialogInterface.OnClickListener() {
                public void onClick(final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
                     dialog.cancel();
                }
            });
     final AlertDialog alert = builder.create();
     alert.show();
 }     
     
     private void launchGPSOptions() {

 		try {
 			startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
 		} catch (Exception e) {
 			try {
 				startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
 			} catch (Exception ex) {
 				debugToast("Double exception (in Sensors.launchGPSOptions)!  All the way!  What does it mean?!");
 			}
 		}
 	}     
     
     // Gets the true username.
     // TODO: make it actually work.
     public static String getUsername() {
    	 return "Phunkatronixxx";
     }
     
     
     // For trancribing a single int's bytecode to some output.
     public static byte[] intToByteArray(int param) {
	    byte[] result = new byte[4];
    	for (int i = 0; i < 4; i++) {
    		int offset = (result.length - 1 - i) * 8;
    		result[i] = (byte) ((param >>> offset) & MASK);
    	}
    	return result;
    }
     
     // For transcribing a single long integer's bytecode to some output.
     public static byte[] longToByteArray(long param) {
 	    byte[] result = new byte[8];
     	for (int i = 0; i < 8; i++) {
     		int offset = (result.length - 1 - i) * 8;
     		result[i] = (byte) ((param >>> offset) & MASK);
     	}
     	return result;
     }

     // For transcribing a float.  It is the same size as an int.
     private byte[] floatToByteArray(float f) {
    	 int i = Float.floatToRawIntBits(f);
    	 return intToByteArray(i);
     }
     
     // ye olde 3-d hypotenuse.
     private float twoNorm(float x, float y, float z) {
    	 return (float) java.lang.Math.sqrt(x * x + y * y + z * z);
     }
     
     private float twoNorm(float x, float y) {
    	 return (float) java.lang.Math.sqrt(x * x + y * y);
     }
     
     // packets are made of time, position, and the first three derivatives: velocity, acceleration, and jerk.
     // So, a packet looks like this: (all values are floats except time)
     // time
     // pos_x, pos_y, pos_z, delta_distance, total_distance
     // vel_x, vel_y, vel_z, delta_velocity, total_velocity
     // acc_x, acc_y, acc_z, delta_acc,      total_acc (this may be an absolute value)
     // jerkx, jerky, jerkz, delta_jerk,     total_jerk (also probably an absolute value)
     
     // Time is the simplest thing to write, as it is a single item.
     private void writeTime(long t) throws IOException {
    	 fos.write(longToByteArray(t));
     }
     
     private void writePosAndVel(float x, float y) throws IOException {
    	 float deltax = x - lastx;
    	 float deltay = y - lasty;
    	 float deltad = twoNorm(deltax, deltay);
    	 //float totald = twoNorm(x-x0, y-y0, z-z0);
    	 
    	 fos.write(floatToByteArray(x));
    	 fos.write(floatToByteArray(y));
    	 fos.write(floatToByteArray(deltad));
    	 lastx = x;
    	 lasty = y;
    	 
    	 float vx = deltax / deltat;
    	 float vy = deltay / deltat;
    	 //float totalv = totald / (lastt - t0);
    	 fos.write(floatToByteArray(vx));
    	 fos.write(floatToByteArray(vy));
    	 // instantaneous velocity (according to the GPS)
    	 fos.write(floatToByteArray(deltad / deltat));

    	 
     }
     
     private void writeAccelAndJerk(float ax, float ay, float az) throws IOException {
    	 float deltaax = ax - lastax;
    	 float deltaay = ay - lastay;
    	 float deltaaz = az - lastaz;
    	 float deltaa  = twoNorm(deltaax, deltaay, deltaaz);
    	 float a       = twoNorm(ax, ay, az);
    	 float j       = deltaa / deltat;
    	 totala = totala + a;
    	 totalj = totalj + j;
    	 
    	 
    	 fos.write(floatToByteArray(ax));
    	 fos.write(floatToByteArray(ay));
    	 fos.write(floatToByteArray(az));
    	 fos.write(floatToByteArray(a));
    	 
    	 fos.write(floatToByteArray(deltaax / deltat));
    	 fos.write(floatToByteArray(deltaay / deltat));
    	 fos.write(floatToByteArray(deltaaz / deltat));
    	 fos.write(floatToByteArray(deltaa  / deltat));
     }
     
     // addAccelMeasurement now also writes all the data it can to a file
     private void addAccelMeasurement(float ax, float ay, float az, long t) {
    	 deltat = t-lastt;
    	 if(t0 == 0) {t0 = t; deltat = 0;}
    	 lastt = t;
    	 
    	 accelMeasurements.insertElementAt(new Measurement(ax, ay, az, deltat), 0);
    	 // jeremy's test code
    	 // cast float to byte array
    	 if (deltat != 0 && intrip) {
    		 try {
				writeTime(t);
				writePosAndVel(1.0f, 2.0f);
				writeAccelAndJerk(ax, ay, az);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	 }
    	 
    	 if(accelMeasurements.size() > MAXPOINTS) accelMeasurements.setSize(MAXPOINTS);
    	 Thread.yield();
 		  viewData.postInvalidate();  	    	 
     }
     
     private void addMagnetMeasurement(float x, float y, float z, long t) {
    	 deltat = t-lastt;
    	 if(t0 == 0) {t0 = t; deltat = 0;}
    	 lastt = t;
    	 magnetMeasurements.insertElementAt(new Measurement(x,y,z,deltat), 0);
    	 if(magnetMeasurements.size() > MAXPOINTS) magnetMeasurements.setSize(MAXPOINTS);
    	 Thread.yield();
 		  viewData.postInvalidate();  	    	 
     }
     
     private final SensorEventListener mSensorListener = new SensorEventListener() {
         
          public void onSensorChanged(SensorEvent se)
          {
              float x = se.values[0];
              float y = se.values[1];
              float z = se.values[2];
              long t = se.timestamp;
              int sensorType = se.sensor.getType();
              if(sensorType == Sensor.TYPE_ACCELEROMETER) {
            	  if(lastSensorMeasurementType == Sensor.TYPE_ACCELEROMETER) return;
            	  addAccelMeasurement(x, y, z, t);
            	  lastSensorMeasurementType = sensorType;     	  
              } else if(sensorType == Sensor.TYPE_MAGNETIC_FIELD) {
            	  if(lastSensorMeasurementType == Sensor.TYPE_MAGNETIC_FIELD) return;
            	  addMagnetMeasurement(x, y , z, t); 
            	  lastSensorMeasurementType = sensorType;
              } else if(sensorType == Sensor.TYPE_GYROSCOPE) {
            	  //System.out.println("");
              }
          }
         
          public void onAccuracyChanged(Sensor sensor, int accuracy) {}
     };
       
     
     
     public void upload() {
    	 //stop writing to file and upload it
    	 intrip = false;
    	 try {
			fos.close();
		 } catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		 }
		 HttpClient httpclient = new DefaultHttpClient();
		 httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);

		 HttpPost httppost = new HttpPost("http://copilotcaltech.appspot.com/uploadhandle");
		 File file = new File("test_file");

		 MultipartEntity mpEntity = new MultipartEntity();
		 ContentBody cbFile = new FileBody(file, "application/octet-stream");
		 mpEntity.addPart("userfile", cbFile);


		 httppost.setEntity(mpEntity);

		 HttpResponse response;
		try {
			response = httpclient.execute(httppost);
			HttpEntity resEntity = response.getEntity();
			if (resEntity != null) {
			    System.out.println(EntityUtils.toString(resEntity));
			}
			if (resEntity != null) {
			    resEntity.consumeContent();
			}
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		 

		 httpclient.getConnectionManager().shutdown();
		
     }

     /* Creates the menu items */
     public boolean onCreateOptionsMenu(Menu menu) {
    	 //menu.add(0, 0, 0, "Loopback On/Off");
		 menu.add(0, 0, 0, "Exit");
		 menu.add(0,1,0,"Stop/Upload");
         return true;
     }

     /* Handles item selections */
     public boolean onOptionsItemSelected(MenuItem item) {
    	 int itemId = item.getItemId();
    	 if(itemId == 0) {
    		 terminate();
    	 } else if(itemId == 1) {
    		 upload();
    	 }
         return false;
     }
     
     @Override
     protected void onResume()
     {
          super.onResume();
          mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorRate);
          mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),SensorRate);
          
     }
     
     @Override
     protected void onStop()
     {
          // I changed this because it said to call the super.method first always.  Problem?
    	 
          super.onStop();
          mSensorManager.unregisterListener(mSensorListener);
          debugToast("onStop");
     }
     

     @Override
     protected void onDestroy()
     {
    	 super.onDestroy();
    	 debugToast("onDestroy");
     }

     public void terminate() {
         // I insist that the app is fully terminated
         finish();
         System.exit(0);
     }   
     
     private static class ViewData extends View {
         private Paint   mPaint = new Paint();
         

  
         public ViewData(Context context) {
             super(context);            

             setWillNotDraw(false);
             
         }
         

    
         @Override protected void onDraw(Canvas canvas) {
             
         	
        	 if((accelMeasurements == null || accelMeasurements.size() < 1) &&
        			 (magnetMeasurements == null || magnetMeasurements.size() < 1)	 ) return;
         	
         	RectF xR, yR, zR, tR;
         	
         	Paint paint = mPaint;
            canvas.drawColor(Color.BLACK);
            
            paint.setStrokeWidth(2);
             
             
             int height = this.getHeight();
             int width = this.getWidth();
             int border = 5;
             int h = (height-4*border)/3;
             int w = width - 2*border;

             
             xR = new RectF((float)border,(float)border,(float)(border+w),(float)(border+h));
             yR = new RectF((float)border,(float)(2*border+h),(float)(border+w),(float)(2*border+2*h));
             zR = new RectF((float)border,(float)(3*border+2*h),(float)(border+w),(float)(3*border+3*h));
             //tR = new RectF((float)border,(float)(4*border+3*h),(float)(border+w),(float)(4*border+4*h));
             
             float vscaleAccel = (float) h / (2.f*MAXACCEL);
             float vscaleMagnet = (float) h / (2.f*MAXMAGNET);

             
             int stept = (int)((float) MAXPOINTS / (float) w);
             stept = 1;
             
             paint.setColor(Color.DKGRAY);
             canvas.drawRect(xR, paint);
             canvas.drawRect(yR, paint);
             canvas.drawRect(zR, paint);
             paint.setColor(Color.GRAY);
             
             //canvas.drawRect(tR,paint);
 
             Vector meas = accelMeasurements;
             float vscale = vscaleAccel;
             
             float xposlabel = xR.bottom;
             float yposlabel = yR.bottom;
             float zposlabel = zR.bottom;
             String labelPre = "Accel ";
             int lineColor = Color.YELLOW;
             
             for(int im=0;im<2;im++) {
             

            	 
            	 if(im == 1) {
            		 vscale = vscaleMagnet;
            		 meas = magnetMeasurements;
            		 xposlabel = xR.top + 10;
            		 yposlabel = yR.top + 10;
            		 zposlabel = zR.top + 10;
            		 lineColor = Color.MAGENTA;
            		 labelPre = "Magnet ";
            	 }
            	 if(meas == null || meas.size() < 1) continue;
            	 
            	 int ipos = 0;  
             
            	 Measurement m = (Measurement) meas.elementAt(ipos++);
        	 
            	 paint.setColor(lineColor);
            	 
            	 canvas.drawText(labelPre+" X = "+m.x,xR.left,xposlabel, paint);
            	 canvas.drawText(labelPre+" Fwd/Back = "+m.y,yR.left,yposlabel, paint);
            	 canvas.drawText(labelPre+" Z = "+m.z,zR.left,zposlabel, paint);
             
            	 paint.setStrokeWidth(1);

            	 int oldtpos = border + w;
                	 
            	 int oldx = border + h/2 - (int)(m.x*vscale);
            	 int oldy = 2*border + (3*h/2) - (int)(m.y*vscale);
            	 int oldz = 3*border + (5*h/2) - (int)(m.z*vscale);
            	 //int oldt = 4*border + (8*h/2) - (int)((float)m.t*vscalet);
                      
            	 //canvas.drawText((int)(1000000000.f/(float)deltat)+" Hz", border, height-5*border, paint);

             
            	 while(ipos < meas.size() && oldtpos > border) {
            		 m = (Measurement) meas.elementAt(ipos);
            		 int x = border + h/2 - (int)(m.x*vscale);
            		 if( x < border) x = border;
            		 if( x > border + h) x = border + h;
            		 int y = 2*border + (3*h/2) - (int)(m.y*vscale);
            		 if( y < 2*border + h) y = 2*border + h;
            		 if( y > 2*border + 2*h) y = 2*border + 2*h;
            		 int z = 3*border + (5*h/2) - (int)(m.z*vscale);
            		 if (z < 3*border + 2*h) z = 3*border + 2*h;
            		 if( z > 3*border + 3*h) z = 3*border + 3*h;
            		 //int t = 4*border + (8*h/2) - (int)((float)m.t*vscalet);
            		 //if (t < tR.top) t = (int) tR.top;
            		 int tpos = oldtpos - 1;
            		 canvas.drawLine(oldtpos,oldx,tpos,x, paint);
            		 canvas.drawLine(oldtpos,oldy,tpos,y, paint);
            		 canvas.drawLine(oldtpos,oldz,tpos,z, paint);
            		 //canvas.drawLine(oldtpos,oldt,tpos,t, paint);
            	 
            		 oldx = x;
            		 oldy = y;
            		 oldz = z;
            		 //oldt = t;
            		 oldtpos = tpos;
            	 
            		 ipos += stept;
            	 
            	 }
             } 
 
         }
     	
     }
     
     private static class Measurement {
    	 public float x,y,z;
    	 public long t; 
    	 public Measurement(float _x, float _y, float _z, long _t) {x = _x; y = _y; z = _z; t = _t;}
     }     
}

